package com.feather.tenia.adapter;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.feather.common.data.Column;
import com.feather.common.data.ColumnCollection;
import com.feather.common.data.DataItem;
import com.feather.common.data.DataSet;
import com.feather.common.data.simple.SimpleDataItemCollection;
import com.feather.common.data.simple.SimpleDataSet;
import com.feather.common.db.DbFetcher;
import com.feather.common.db.DbSaver;
import com.feather.common.db.MappingNotFoundException;
import com.feather.common.db.TypeMapping;
import com.feather.common.log.LogProvider;
import com.feather.common.log.Logger;
import com.feather.tenia.adapter.fetcher.NormalFetcher;
import com.feather.tenia.adapter.saver.NormalSaver;
import com.feather.tenia.adapter.util.CloseUtil;
import com.feather.tenia.ExecuteCallback;
import com.feather.tenia.Procedure;
import com.feather.tenia.ProcedureMeta;
import com.feather.tenia.TeniaHelper;
import com.feather.tenia.Tenia;

/**
 * @author flogyin
 */
public abstract class ProcedureAdapter implements Procedure {
	protected Tenia			dbTenia;
	protected ProcedureMeta	meta;

	public ProcedureAdapter(Tenia dbTenia, ProcedureMeta meta) {
		this.dbTenia = dbTenia;
		this.meta = meta;
	}

	@Override
	public Tenia getTenia() {
		return this.dbTenia;
	}

	@Override
	public ProcedureMeta getMeta() {
		return this.meta;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected DataSet call(ParameterValue pv) throws SQLException, Exception {
		LinkedHashMap<String, Long> duration = new LinkedHashMap<String, Long>();
		long srms = System.currentTimeMillis();

		LinkedHashMap matchedParams = new LinkedHashMap();
		ColumnCollection columnCollection = this.meta.getColumns();
		int columnsCount = columnCollection.getCount();

		SimpleDataSet dataSet = new SimpleDataSet(new SimpleDataItemCollection());
		dataSet.setName(this.meta.getPrintName());

		String callSql = this.meta.getCallSql();
		Tenia tenia = getTenia();
		TypeMapping typeMapping = tenia.getTypeMapping();
		ExecuteCallback callback = this.dbTenia.getExecuteCallback();
		CallableStatement statement = null;
		Connection connection = tenia.getConnector().getConnection();
		try {
			statement = connection.prepareCall(callSql);

			long crms = System.currentTimeMillis();
			duration.put("prepare", crms - srms);
			srms = crms;

			for (int i = 0; i < columnsCount; i++) {
				Column column = columnCollection.get(i);
				String columnName = column.getName();
				switch (column.getType()) {
					case DatabaseMetaData.procedureColumnReturn: {
						statement.registerOutParameter(i + 1, column.getDataType());
						break;
					}
					case DatabaseMetaData.procedureColumnIn: {
						if (column.getOrdinal() > 0) {
							Object paramValue = pv.getValue(columnName);
							if (paramValue instanceof String[]) {
								String[] arr = (String[]) paramValue;
								paramValue = arr.length > 0 ? arr[0] : null;
							}
							matchedParams.put(columnName, paramValue);
							Class paramValueType = paramValue != null ? paramValue.getClass() : null;
							DbSaver saver = typeMapping.lookup(paramValueType, column.getDataTypeName());
							if (saver == null) {
								if (this.dbTenia.isCheckSaver()) {
									throw new MappingNotFoundException("Not found DbSaver by: " + column.getName());
								}
								saver = NormalSaver.INSTANCE;
							}
							saver.save(paramValue, statement, column);
						}
						break;
					}
					case DatabaseMetaData.procedureColumnInOut: {
						Object paramValue = pv.getValue(columnName);
						if (paramValue instanceof String[]) {
							String[] arr = (String[]) paramValue;
							paramValue = arr.length > 0 ? arr[0] : null;
						}
						matchedParams.put(columnName, paramValue);
						Class paramValueType = paramValue != null ? paramValue.getClass() : null;
						DbSaver saver = typeMapping.lookup(paramValueType, column.getDataTypeName());
						if (saver == null) {
							if (this.dbTenia.isCheckSaver()) {
								throw new MappingNotFoundException("Not found DbSaver by: " + column.getName());
							}
							saver = NormalSaver.INSTANCE;
						}
						saver.save(paramValue, statement, column);

						statement.registerOutParameter(i + 1, column.getDataType());
						break;
					}
					case DatabaseMetaData.procedureColumnOut: {
						statement.registerOutParameter(column.getOrdinal(), column.getDataType());
						break;
					}
				}
			}
			crms = System.currentTimeMillis();
			duration.put("procedureColumn", crms - srms);
			srms = crms;
			if (callback != null) {
				callback.onMatchProcedure(this, matchedParams);
			}
			crms = System.currentTimeMillis();
			duration.put("onMatch", crms - srms);
			srms = crms;

			long[] exeMs = execute(statement, dataSet);
			duration.put("execute", exeMs[0]);

			srms = System.currentTimeMillis();
			for (int i = 0; i < columnsCount; i++) {
				Column column = this.meta.getColumns().get(i);
				switch (column.getType()) {
					case DatabaseMetaData.procedureColumnReturn:
					case DatabaseMetaData.procedureColumnInOut:
					case DatabaseMetaData.procedureColumnOut: {
						DbFetcher fetcher = typeMapping.lookup(column.getDataTypeName());
						if (fetcher == null) {
							if (this.dbTenia.isCheckFetcher()) {
								throw new MappingNotFoundException("Not found DbFetcher by: " + column.getName());
							}
							fetcher = NormalFetcher.INSTANCE;
						}
						DataItem dataItem = fetcher.fetch(statement, column);
						dataSet.getResult().add(dataItem);
						break;
					}
				}
			}
			crms = System.currentTimeMillis();
			duration.put("fetcher", crms - srms + exeMs[1]);
		} finally {
			CloseUtil.close(statement, connection);
		}

		Logger logger = LogProvider.CURRENT[0].createPrinter();
		logger.setSource(this.getMeta().getOriginal());
		logger.setMethod("duration");
		logger.setMessage(duration);
		logger.print();
		return dataSet;
	}

	@Override
	public DataSet call() throws SQLException, Exception {
		class ParameterValueImpl implements ParameterValue {
			public Object getValue(String name) throws SQLException {
				return null;
			}
		}
		return call(new ParameterValueImpl());
	}

	@Override
	public DataSet call(Object[] parameters) throws SQLException, Exception {
		TeniaHelper helper = this.dbTenia.getHelper();
		if (helper != null) {
			parameters = helper.reviseProcedureParameter(this, parameters);
		}
		class ParameterValueImpl implements ParameterValue {
			private Object[]	parameters;

			ParameterValueImpl(Object[] p) {
				this.parameters = p;
			}

			public Object getValue(String name) throws SQLException {
				if (name != null) {
					int length = this.parameters != null ? this.parameters.length : 0;
					for (int i = 0; i < length; i += 2) {
						String paramName = (String) this.parameters[i];
						if (name.equalsIgnoreCase(paramName)) {
							int temp = i + 1;
							if (temp < length)
								return this.parameters[temp];
						}
					}
					// throw new SQLException("Not found parameter value by: "+
					// name);
				}
				return null;
			}
		}
		ParameterValueImpl impl = new ParameterValueImpl(parameters);
		return call(impl);
	}

	@Override
	public DataSet call(List<Object> parameters) throws SQLException, Exception {
		TeniaHelper helper = this.dbTenia.getHelper();
		if (helper != null) {
			parameters = helper.reviseProcedureParameter(this, parameters);
		}
		class ParameterValueImpl implements ParameterValue {
			private List<Object>	parameters;

			ParameterValueImpl(List<Object> p) {
				this.parameters = p;
			}

			public Object getValue(String name) throws SQLException {
				if (name != null) {
					int size = this.parameters != null ? this.parameters.size() : 0;
					for (int i = 0; i < size; i += 2) {
						String paramName = (String) this.parameters.get(i);
						if (name.equalsIgnoreCase(paramName)) {
							int temp = i + 1;
							if (temp < size)
								return this.parameters.get(temp);
						}
					}
					// throw new SQLException("Not found parameter value by: "+
					// name);
				}
				return null;
			}
		}
		ParameterValueImpl impl = new ParameterValueImpl(parameters);
		return call(impl);
	}

	@Override
	public DataSet call(Map<String, Object> parameters) throws SQLException, Exception {
		TeniaHelper helper = this.dbTenia.getHelper();
		if (helper != null) {
			parameters = helper.reviseProcedureParameter(this, parameters);
		}
		class ParameterValueImpl implements ParameterValue {
			private Map<String, Object>	parameters;

			ParameterValueImpl(Map<String, Object> p) {
				this.parameters = p;
			}

			public Object getValue(String name) throws SQLException {
				if (name != null) {
					for (Map.Entry<String, Object> entry : this.parameters.entrySet()) {
						String key = entry.getKey();
						if (name.equalsIgnoreCase(key))
							return entry.getValue();
					}
					// throw new SQLException("Not found parameter value by: "+
					// name);
				}
				return null;
			}
		}
		ParameterValueImpl impl = new ParameterValueImpl(parameters);
		return call(impl);
	}

	/*
	 * @return long, 执行时间
	 */
	protected abstract long[] execute(CallableStatement statement, DataSet dataSet) throws SQLException;

	public static interface ParameterValue {
		Object getValue(String name) throws SQLException;
	}
}
/*
 * 仅返回与模式和过程名称标准匹配的过程。它们根据 PROCEDURE_SCHEM 和 PROCEDURE_NAME 进行排序。
 * 
 * 每个过程描述都有以下列：
 * 
 * 1.PROCEDURE_CAT String => 过程类别（可为 null） 2.PROCEDURE_SCHEM String => 过程模式（可为
 * null） 3.PROCEDURE_NAME String => 过程名称 4.保留供以后使用 5.保留供以后使用 6.保留供以后使用 7.REMARKS
 * String => 过程的解释性注释 8.PROCEDURE_TYPE short => 过程的种类： ◦procedureResultUnknown -
 * 可能返回结果 ◦procedureNoResult - 不返回结果 ◦procedureReturnsResult - 返回结果
 * 
 * 参数： catalog - 类别名称，因为存储在数据库中，所以它必须匹配类别名称。该参数为 "" 则检索没有类别的描述，为 null
 * 则表示该类别名称不应用于缩小搜索范围 schemaPattern - 模式名称的模式，因为存储在数据库中，所以它必须匹配模式名称。该参数为 ""
 * 则检索那些没有模式的描述，为 null 则表示该模式名称不应用于缩小搜索范围 procedureNamePattern -
 * 过程名称模式，因为存储在数据库中，所以它必须匹配过程名称 返回： ResultSet - 每个行都是一个过程描述 抛出： SQLException -
 * 如果发生数据库访问错误
 */